package ch.cryptobox.files;

import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.FileVisitResult.SKIP_SUBTREE;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import ch.cryptobox.filehandles.FileHandleI;
import ch.cryptobox.serivces.RemoteFileService;

public class FileHelper {	
	static void printFileTree(final String path) {
		final Path root = FileSystems.getDefault().getPath(path).toAbsolutePath().normalize();
		try {

			Files.walkFileTree(root, new FilePrinter());
		} catch (final IOException e) {
			e.printStackTrace();
		}
	}

	public static ArrayList<File> getNotSynchedFiles(final String localMountPoint, final RemoteFileService service) {
		final SynchronizationVisitor visitor = new SynchronizationVisitor(localMountPoint, service);
		
		try {
			Files.walkFileTree(FileSystems.getDefault().getPath(localMountPoint).toAbsolutePath().normalize(), visitor);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return visitor.toSynchronize;
	}


	public static ArrayList<Path> getSubFolderPaths(final Path path) {
		try {
			final SubFolderFinder finder = new SubFolderFinder();
			Files.walkFileTree(path.toAbsolutePath().normalize(), finder);
			return finder.getSubfolders();
		} catch (final IOException e) {
			e.printStackTrace();
		}
		return new  ArrayList<Path>();
	}
	
	
	
	public static ArrayList<Path> getFiles(final Path path) {
		try {
			final FileFinder finder = new FileFinder();
			Files.walkFileTree(path.toAbsolutePath().normalize(), finder);
			return finder.getFiles();
		} catch (final IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	
	private static class FileFinder extends VisibleFileVisitor {
		private final ArrayList<Path> files = new ArrayList<Path>();
		
		//TODO
		
		
		public ArrayList<Path> getFiles()  {
			return files;
		}
		
		
	}
	

	private static class SynchronizationVisitor extends VisibleFileVisitor {
		final HashMap< String, FileHandleI > fileHandleMap = new HashMap< String, FileHandleI >();
		final ArrayList<File> toSynchronize = new ArrayList<File>();
		final String localMountPoint;
		
		SynchronizationVisitor(final String localMountPoint, final RemoteFileService service) {
			this.localMountPoint = localMountPoint;
			auxGetRemoteFiles(service.getRootHandle());

		}

		private void auxGetRemoteFiles(final FileHandleI handle) {
			fileHandleMap.put(FileSystems.getDefault().getPath(localMountPoint + handle.getPath()).toAbsolutePath().normalize().toString(), handle);
			
			if(handle.isDirectory()) {
				final ArrayList<FileHandleI> handles = handle.getFiles();
				if(handles == null)
					return;
				for(final FileHandleI h : handles) {
					auxGetRemoteFiles(h);
				}
			} else {
				//System.out.println((localMountPoint + handle.getPath()).trim());
			}
		}

		private void addIfNotSynched(final Path file) {
			String name = file.toAbsolutePath().normalize().toString();			
			FileHandleI handle = fileHandleMap.get(name);
			if(handle != null && (handle.isRoot() || !handle.getDate().before(new Date(file.toFile().lastModified())))) {
				//System.out.format("%s is synched\n", file);
			} else {
				//System.out.format("%s is not synched\n", file);
				toSynchronize.add(file.toFile());
			}
		}

		public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr) {
			try {
				if(Files.isHidden(file))
					return CONTINUE;
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			if (attr.isSymbolicLink()) {
				//System.out.format("Symbolic link: %s ", file);
				return FileVisitResult.SKIP_SUBTREE;

			} else if (attr.isRegularFile()) {
				addIfNotSynched(file);
			} else {
				System.out.format("Other: %s ", file);
			}
			return CONTINUE;
		}
		
		
		public FileVisitResult postVisitDirectory(final Path path, final IOException exc) {

			if(null != exc) {
				System.err.println(exc);
				return CONTINUE;
			}

			addIfNotSynched(path);
			return CONTINUE;
		}
	}
	
	



	private static class VisibleFileVisitor extends SimpleFileVisitor<Path> {
		@Override
		public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) {
			if(dir.toFile().isHidden())
				return SKIP_SUBTREE; 
			return CONTINUE;
		}
	}


	private static class SubFolderFinder extends VisibleFileVisitor {
		private final ArrayList<Path> subFolders = new ArrayList<Path>();

		public ArrayList<Path> getSubfolders()
		{
			return subFolders;
		}

		public FileVisitResult postVisitDirectory(final Path path, final IOException exc) {

			if(null != exc) {
				System.err.println(exc);
				return CONTINUE;
			}

			subFolders.add(path);
			return CONTINUE;
		}
	}



	private static class FilePrinter extends VisibleFileVisitor {
		//Print information about each type of file.
		@Override
		public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr) {
			if (attr.isSymbolicLink()) {
				System.out.format("Symbolic link: %s ", file);
				return FileVisitResult.SKIP_SUBTREE;

			} else if (attr.isRegularFile()) {
				System.out.format("Regular file: %s ", file);
			} else {
				System.out.format("Other: %s ", file);
			}


			System.out.println("(" + attr.size() + "bytes)");
			return CONTINUE;
		}


		//Print each directory visited.
		@Override
		public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) {
			System.out.format("Directory: %s%n", dir);
			return CONTINUE;
		}

		//If there is some error accessing the file, let the user know.
		//If you don't override this method and an error occurs, an IOException 
		//is thrown.
		@Override
		public FileVisitResult visitFileFailed(final Path file, final IOException exc) {
			System.err.println(exc);
			return CONTINUE;
		}
	}
}
